/*
 * Copyright (C) 1998, 2009  John Pritchard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package loader.sandbox ;

/**
 * ClassFile is a class file rewriter for reading a bytecode class file,
 * manipulating it, and then copying it out for loading (definition in
 * the class loader).  ClassFile is loosely based on Chuck McManus'
 * "ClassFile" program.
 *
 * <p> The class file constant pool contains (most notably) field and
 * method references, and declared field constant values.  A compiled
 * class can be read as a resource, renamed, have references and
 * values modified, and then can be loaded for a fast dynamic
 * reference.  
 *
 * <p> The overhead of creating this dynamic class is on order of a
 * hundred times greater than getting a method or field using Java
 * reflection, but the resulting runtime performance is on order of a
 * hundred times greater than reflected methods and fields.
 * 
 * <p> <b>Command line</b>
 * 
 * <p> This class includes a simple command line function for printing
 * basic class file data.  The perspective offered describes the class
 * file structure.  
 *
 * <p> At the top of its output, the file magic identifier and version
 * are represented.  On the following lines, ClassFile prints the
 * identifying attributes of the class, then its fields and methods,
 * and then the constant pool.  (In the class file, the constant pool
 * comes before the class information, after the file magic identifier
 * and version.)
 * 
 * <pre>
 * 	CAFEBABE 3.45
 * 	Class      CLASS {37 alto/pi/pif/pica}
 * 	Superclass CLASS {38 java/lang/Object}
 * 	Interface  CLASS {39 alto/pi/pif/pico}
 * 
 * 	Field {12 target_classname}
 * 
 * 	Method {15 &lt;init&gt; @{Code}}
 * 
 * 	1:	METHODREF {10 CLASS {38 java/lang/Object}}{27 NAME&amp;TYPE {15 &lt;init&gt;}{16 ()V}}
 * 	2:	STRING {28 alto.pi.pif.pica_}
 * 	3:	FIELDREF {9 CLASS {37 alto/pi/pif/pica}}{29 NAME&amp;TYPE {12 target_classname}{13 Ljava/lang/String;}}
 * 	4:	STRING {30 pica_fun}
 * 	5:	FIELDREF {9 CLASS {37 alto/pi/pif/pica}}{31 NAME&amp;TYPE {14 target_function}{13 Ljava/lang/String;}}
 * 	6:	METHODREF {32 CLASS {40 alto/pi/pif/pica_}}{33 NAME&amp;TYPE {30 pica_fun}{20 (Ljava/lang/Object;)Ljava/lang/Object;}}
 * 	7:	METHODREF {34 CLASS {41 java/lang/Class}}{35 NAME&amp;TYPE {42 forName}{43 (Ljava/lang/String;)Ljava/lang/Class;}}
 * 	8:	CLASS {36 java/lang/ClassNotFoundException}
 * 	9:	CLASS {37 alto/pi/pif/pica}
 * 	10:	CLASS {38 java/lang/Object}
 * 	11:	CLASS {39 alto/pi/pif/pico}
 * 	12:	target_classname
 * 
 * 	14:	target_function
 * 	15:	&lt;init&gt;
 * 	16:	()V
 * </pre>
 * 
 * <p> 
 * 
 * <p> <b>Usage</b>
 * 
 * <p> Without using ClassFile, a dynamic function invocation is done using
 * the reflection API, like so.
 * 
 * <pre>
 *   Class cla = Class.forName(clnam)
 *   Method invoker = cla.getDeclaredMethod(metnam,argtypes)
 * </pre>
 * 
 * <p> Using ClassFile, a class with a generic invocation method invoking a
 * dummy target class and function are defined.  At runtime, the
 * generic class is read as a resource, and the dummy reference
 * replaced with a dynamically configured reference.
 * 
 * <pre>
 *  package foo.bar
 * 
 *  public class generic {
 *
 *    public Object invoke ( Object arg){
 *      return dummy.invoke(arg)
 *    }
 *  }
 *
 *  public class dummy {
 *
 *    public Object invoke ( Object arg){
 *      return null
 *    }
 *  }
 * </pre>
 * 
 * <p> The following is an outline of the method adaptation process
 * using ClassFile.
 * 
 * <pre>
 *   ClassLoader cl = obj.getClass().getClassLoader()
 *   InputStream rin = cl.getResourceAsStream('/foo/bar/dummy.class')
 * 
 *   clar clr = new clar(rin)
 *   clr.subClass('generated.a123')
 *   clr.renameMethodRef('foo.bar.dummy.invoke','com.pack.cla.funName')
 *   byte[] classfile = clr.write()
 * 
 *   cl.defineClass( classfile)
 * 
 *   Class gencla = Class.forName('generated.a123')
 *   method invoker = gencla.newInstance()
 * </pre>
 * 
 * <p> If the method will only be invoked once or twice, the
 * reflection approach is certainly much more efficient.  Likewise, an
 * infrequently called method benefits very little from a fast
 * invocation.
 * 
 * @author John Pritchard 
 * @since 1.1
 */
public class ClassFile {

    public static final byte TYPE_UTF8        =  1;
    public static final byte TYPE_INTEGER     =  3;
    public static final byte TYPE_FLOAT       =  4;
    public static final byte TYPE_LONG        =  5;
    public static final byte TYPE_DOUBLE      =  6;
    public static final byte TYPE_CLASS       =  7;
    public static final byte TYPE_STRING      =  8;
    public static final byte TYPE_FIELDREF    =  9;
    public static final byte TYPE_METHODREF   = 10;
    public static final byte TYPE_IFMETREF    = 11;
    public static final byte TYPE_NAMEANDTYPE = 12;

    public static final String TYPENAME_CLASS       = "CLASS";
    public static final String TYPENAME_FIELDREF    = "FIELDREF";
    public static final String TYPENAME_METHODREF   = "METHODREF";
    public static final String TYPENAME_IFMETREF    = "IFMETREF";
    public static final String TYPENAME_NAMEANDTYPE = "NAME&TYPE";
    public static final String TYPENAME_STRING      = "STRING";

    public static final short ACC_PUBLIC        = 0x0001;
    public static final short ACC_PRIVATE       = 0x0002;
    public static final short ACC_PROTECTED     = 0x0004;
    public static final short ACC_STATIC        = 0x0008;
    public static final short ACC_FINAL         = 0x0010;
    public static final short ACC_SYNCHRONIZED  = 0x0020;
    public static final short ACC_NATIVE        = 0x0100;
    public static final short ACC_ABSTRACT      = 0x0400;
    public static final short ACC_STRICT        = 0x0800;



    private final static short BEshort(byte a[]){

        int a0 = ((a[0])<<8);

        int a1 = a[1];

        return (short)(a0|a1);
    }



    /**
     * Tree node for the class data.
     */
    public static class CPInfo {
	
        protected int type = -1;

        protected short arg1_idx = -1, arg2_idx = -1;

        protected String strValue = null;

        protected int intValue;
        protected long longValue;
        protected float floatValue;
        protected double doubleValue;
	
        public CPInfo(){}

        public CPInfo arg1( CPInfo[] pool){
            return pool[arg1_idx];
        }

        public CPInfo arg2( CPInfo[] pool){
            return pool[arg2_idx];
        }

        public String getArg1(ClassFile classfile){
            return classfile.pool(this.arg1_idx);
        }
        public String getArg2(ClassFile classfile){
            return classfile.pool(this.arg2_idx);
        }
        public String getClass(ClassFile classfile){
            return classfile.poolClass(this.arg1_idx);
        }
        public String getName(ClassFile classfile){
            return classfile.poolName(this.arg1_idx);
        }
        public String getType(ClassFile classfile){
            return classfile.poolType(this.arg2_idx);
        }

        public void read(java.io.DataInputStream din) throws java.io.IOException {

            type = din.readByte(); // CP info tag

            switch (type){
            case TYPE_UTF8:
                StringBuilder strbuf = new StringBuilder();

                int len = din.readShort();

                while (len-- > 0)
                    strbuf.append((char)din.readByte());

                strValue = strbuf.toString();
                break;
            case TYPE_INTEGER:
                intValue = din.readInt();
                break;
            case TYPE_FLOAT:
                floatValue = din.readFloat();
                break;
            case TYPE_LONG:
                longValue = din.readLong();
                break;
            case TYPE_DOUBLE:
                doubleValue = din.readDouble();
                break;
            case TYPE_CLASS:
                arg1_idx = din.readShort();
                break;
            case TYPE_STRING:
                arg1_idx = din.readShort();
                break;
            case TYPE_FIELDREF:
                arg1_idx = din.readShort();
                arg2_idx = din.readShort();
                break;
            case TYPE_METHODREF:
                arg1_idx = din.readShort();
                arg2_idx = din.readShort();
                break;
            case TYPE_IFMETREF:
                arg1_idx = din.readShort();
                arg2_idx = din.readShort();
                break;
            case TYPE_NAMEANDTYPE:
                arg1_idx = din.readShort();
                arg2_idx = din.readShort();
                break;
            default:
                throw new java.io.IOException("Bad type `0x"+Integer.toHexString(type)+"'.");
            }
        }
        public void write(java.io.DataOutputStream dout) throws java.io.IOException {
            dout.write(type);
	    
            switch (type){
            case TYPE_UTF8:
                dout.writeShort(strValue.length());
                dout.writeBytes(strValue);
                break;
            case TYPE_INTEGER:
                dout.writeInt(intValue);
                break;
            case TYPE_FLOAT:
                dout.writeFloat(floatValue);
                break;
            case TYPE_LONG:
                dout.writeLong(longValue);
                break;
            case TYPE_DOUBLE:
                dout.writeDouble(doubleValue);
                break;
            case TYPE_CLASS:
            case TYPE_STRING:
                dout.writeShort( arg1_idx);
                break;
            case TYPE_FIELDREF:
            case TYPE_METHODREF:
            case TYPE_IFMETREF:
            case TYPE_NAMEANDTYPE:
                dout.writeShort( arg1_idx);
                dout.writeShort( arg2_idx);
                break;
            default:
                throw new java.io.IOException("Bad type `0x"+Integer.toHexString(type)+"'.");
            }
        }
        public String toString(){

            String name = null;

            switch(type){
		
            case TYPE_UTF8:
                return strValue;
		
            case TYPE_INTEGER:
                return Integer.toString(intValue);
		
            case TYPE_LONG:
                return Long.toString(longValue);
		
            case TYPE_FLOAT:
                return Float.toString(floatValue);
		
            case TYPE_DOUBLE:
                return Double.toString(doubleValue);
		
            case TYPE_CLASS:
                name = TYPENAME_CLASS;
                break;
            case TYPE_STRING:
                name = TYPENAME_STRING;
                break;
            case TYPE_FIELDREF:
                name = TYPENAME_FIELDREF;
                break;
            case TYPE_METHODREF:
                name = TYPENAME_METHODREF;
                break;
            case TYPE_IFMETREF:
                name = TYPENAME_IFMETREF;
                break;
            case TYPE_NAMEANDTYPE:
                name = TYPENAME_NAMEANDTYPE;
                break;
            }
		
            StringBuilder strbuf = new StringBuilder();
		
            strbuf.append(name);
            strbuf.append(' ');

            if ( -1 < arg1_idx){
		
                strbuf.append('{');
                strbuf.append(Integer.toString(arg1_idx));
                strbuf.append('}');
		
                if ( -1 < arg2_idx){
                    strbuf.append('{');
                    strbuf.append(Integer.toString(arg2_idx));
                    strbuf.append('}');
                }
            }
            return strbuf.toString();
        }
        public String toString(ClassFile classfile){
            return this.toString(classfile.pool());
        }
        public String toString( CPInfo[] pool){

            String name = null;

            switch(type){
		
            case TYPE_UTF8:
                return strValue;
		
            case TYPE_INTEGER:
                return Integer.toString(intValue);
		
            case TYPE_LONG:
                return Long.toString(longValue);
		
            case TYPE_FLOAT:
                return Float.toString(floatValue);
		
            case TYPE_DOUBLE:
                return Double.toString(doubleValue);
		
            case TYPE_CLASS:
                name = TYPENAME_CLASS;
                break;
            case TYPE_STRING:
                name = TYPENAME_STRING;
                break;
            case TYPE_FIELDREF:
                name = TYPENAME_FIELDREF;
                break;
            case TYPE_METHODREF:
                name = TYPENAME_METHODREF;
                break;
            case TYPE_IFMETREF:
                name = TYPENAME_IFMETREF;
                break;
            case TYPE_NAMEANDTYPE:
                name = TYPENAME_NAMEANDTYPE;
                break;
            }
		
            StringBuilder strbuf = new StringBuilder();
		
            strbuf.append(name);
            strbuf.append(' ');

            if ( -1 < arg1_idx){
		
                strbuf.append('{');
                strbuf.append(arg1_idx);
                strbuf.append(' ');
                strbuf.append(pool[arg1_idx].toString(pool));
                strbuf.append('}');
		
                if ( -1 < arg2_idx){
                    strbuf.append('{');
                    strbuf.append(arg2_idx);
                    strbuf.append(' ');
                    strbuf.append(pool[arg2_idx].toString(pool));
                    strbuf.append('}');
                }
            }
            return strbuf.toString();
        }
        public boolean equals(CPInfo cp){
	    
            if (cp == null)
		
                return false;
	    
            else if (cp.type != type)
		
                return false;
	    
            else {
                switch (cp.type){
                case TYPE_UTF8:
                    return cp.strValue.equals(strValue);
                case TYPE_INTEGER:
                    return (cp.intValue == intValue);
                case TYPE_FLOAT:
                    return (cp.floatValue == floatValue);
                case TYPE_LONG:
                    return (cp.longValue == longValue);
                case TYPE_DOUBLE:
                    return (cp.doubleValue == doubleValue);
                case TYPE_CLASS:
                case TYPE_STRING:
                    return (arg1_idx == cp.arg1_idx);
                case TYPE_FIELDREF:
                case TYPE_METHODREF:
                case TYPE_IFMETREF:
                case TYPE_NAMEANDTYPE:
                    return ((arg1_idx == cp.arg1_idx) && (arg2_idx == cp.arg2_idx));
                }
		
                return false;
            }
        }
        public CPInfo indexOf( CPInfo pool[]){

            CPInfo cpi;

            for (int cc = 1; cc < pool.length; cc++){

                cpi = pool[cc];

                if (equals(cpi))
                    return cpi;
            }
            return null;
        }
    }



    /**
     * Attribute info
     */
    public static class AttrInfo {

        protected short name_idx ;

        protected byte[] data = null; 

        public AttrInfo(){}


        public String getName(ClassFile classfile){

            return classfile.pool(name_idx);
        }
        public CPInfo getName( CPInfo pool[]){
            return pool[name_idx];
        }

        public boolean booleanValue(CPInfo pool[]){

            CPInfo cpi = pool[BEshort(data)];

            if ( 0 == cpi.intValue)
                return false;
            else
                return true;
        }
        public CPInfo cpValue(CPInfo pool[]){

            return pool[BEshort(data)];
        }
        public void read(java.io.DataInputStream din) throws java.io.IOException {

            name_idx = din.readShort(); 

            int len = din.readInt(), read;

            data = new byte[len];

            if (len != (read = din.read(data,0,len)))
                throw new java.io.IOException("Classfile truncated ("+read+"/"+len+").");
        }
        public void write(java.io.DataOutputStream dout) 
            throws java.io.IOException, java.util.NoSuchElementException 
        {
            dout.writeShort( name_idx);

            dout.writeInt(data.length);

            dout.write(data, 0, data.length);
        }
        public String toString(){
            return "@{"+name_idx+"}";
        }
        public String toString(ClassFile classfile){
            return "@{"+this.getName(classfile)+"}";
        }
        public String toString( CPInfo[] pool){
            return "@{"+pool[name_idx].toString(pool)+"}";
        }
    }



    /**
     * Field info
     */
    public static class FieldInfo {
        protected short access_flags;

        protected short name_idx;

        protected short signature_idx;

        protected AttrInfo attributes[];

        public FieldInfo(){}


        public String getName(ClassFile classfile){

            return classfile.pool(name_idx);
        }
        public CPInfo getName( CPInfo[] pool){

            return pool[name_idx];
        }
        public String getSignature(ClassFile classfile){

            return classfile.pool(signature_idx);
        }
        public CPInfo getSignature( CPInfo[] pool){
            return pool[signature_idx];
        }

        public void read(java.io.DataInputStream din) throws java.io.IOException {
            int count;

            access_flags = din.readShort();

            name_idx = din.readShort();

            signature_idx = din.readShort();

            count = din.readShort();

            if (count != 0){

                attributes = new AttrInfo[count];

                AttrInfo at;

                for (int cc = 0; cc < count; cc++){

                    at = new AttrInfo();

                    attributes[cc] = at;

                    at.read(din);
                }
            }
        }
        public void write(java.io.DataOutputStream dout) 
            throws java.io.IOException 
        {

            dout.writeShort( access_flags);

            dout.writeShort( name_idx);

            dout.writeShort( signature_idx);

            int count = (null == attributes)?(0):(attributes.length);

            dout.writeShort(count);

            if ( 0 < count){

                for ( int cc = 0; cc < count; cc++)

                    attributes[cc].write(dout);
            }
        }
        public String toString(){
            StringBuilder strbuf = new StringBuilder();
            strbuf.append("Field {");
            strbuf.append(name_idx);
            if ( null != attributes){
                int len = attributes.length;

                for ( int cc = 0; cc < len; cc++){
                    strbuf.append(' ');
                    strbuf.append(attributes[cc].toString());
                }
            }
            strbuf.append('}');
            return strbuf.toString();
        }
        public String toString( CPInfo[] pool){
            StringBuilder strbuf = new StringBuilder();
            strbuf.append("Field {");
            strbuf.append(name_idx);
            strbuf.append(' ');
            strbuf.append(pool[name_idx].toString(pool));
            if ( null != attributes){
                int len = attributes.length;

                for ( int cc = 0; cc < len; cc++){
                    strbuf.append(' ');
                    strbuf.append(attributes[cc].toString(pool));
                }
            }
            strbuf.append('}');
            return strbuf.toString();
        }
    }



    /**
     * Method info
     */
    public static class MethodInfo {

        protected short access_flags;

        protected short name_idx ;

        protected short signature_idx ;

        protected AttrInfo attributes[];

        public MethodInfo(){}


        public String getName(ClassFile classfile){

            return classfile.pool(name_idx);
        }
        public CPInfo getName( CPInfo pool[]){
            return pool[name_idx];
        }
        public CPInfo getSignature( CPInfo pool[]){
            return pool[signature_idx];
        }
        public String getSignature(ClassFile classfile){

            return classfile.pool(signature_idx);
        }

        public void read(java.io.DataInputStream din) 
            throws java.io.IOException 
        {

            access_flags = din.readShort();

            name_idx = din.readShort();

            signature_idx = din.readShort();

            int count = din.readShort();

            if (0 < count){

                attributes = new AttrInfo[count];

                AttrInfo at;

                for (int cc = 0; cc < count; cc++){

                    at = new AttrInfo(); // function bytecode 

                    attributes[cc] = at;

                    at.read(din);
                }
            }
        }
        public void write(java.io.DataOutputStream dout) throws java.io.IOException {
            dout.writeShort(access_flags);

            dout.writeShort( name_idx);

            dout.writeShort( signature_idx);

            int count = (null == attributes)?(0):(attributes.length);

            dout.writeShort(count);

            if ( 0 < count){

                for ( int cc = 0; cc < count; cc++)

                    attributes[cc].write(dout);
            }
        }
        public String toString(){
            StringBuilder strbuf = new StringBuilder();
            strbuf.append("Method {");
            strbuf.append(name_idx);
            if ( null != attributes){
                int len = attributes.length;

                for ( int cc = 0; cc < len; cc++){
                    strbuf.append(' ');
                    strbuf.append(attributes[cc].toString());
                }
            }
            strbuf.append('}');
            return strbuf.toString();
        }
        public String toString( CPInfo[] pool){
            StringBuilder strbuf = new StringBuilder();
            strbuf.append("Method {");
            strbuf.append(name_idx);
            strbuf.append(' ');
            strbuf.append(pool[name_idx].toString(pool));
            if ( null != attributes){
                int len = attributes.length;

                for ( int cc = 0; cc < len; cc++){
                    strbuf.append(' ');
                    strbuf.append(attributes[cc].toString(pool));
                }
            }
            strbuf.append('}');
            return strbuf.toString();
        }
    }



    private int magic; // 0xCAFEBABE

    private short version_major;

    private short version_minor;

    private CPInfo[] constant_pool = null;

    private short access_flags;

    private short class_this_idx;

    private short class_super_idx;

    private short[] interface_idxs = null;

    private FieldInfo[] fields = null;

    private MethodInfo[] methods = null;

    private AttrInfo[] attributes = null;

    private boolean _ok = false;


    public ClassFile(){
        super();
    }
    public ClassFile( java.io.File file) throws java.io.IOException {
        this(new java.io.FileInputStream(file));
    }
    /**
     * Read a class file from the input stream
     * 
     * @param in Class file
     *
     * @exception java.io.IOException Bad classfile format.
     */
    public ClassFile( java.io.InputStream in) throws java.io.IOException {
        super();
        read(in);
    }

    /**
     * Read a class file from the input stream
     * 
     * @param in Class file
     *
     * @exception java.io.IOException Bad classfile format.
     */
    public ClassFile( byte[] in) throws java.io.IOException {
        super();
        read(new java.io.DataInputStream(new java.io.ByteArrayInputStream(in)));
    }

    /**
     * Read the named class as a resource.
     *
     * @param rn Resource name for class file, eg, relative to this
     * package: <tt>`pica.class'</tt>; or absolute:
     * <tt>`/tld/dom/pkg/pic2.class'</tt>.
     * 
     * @exception IllegalArgumentException For null argument, or
     * missing class binary, or bad classfile format.
     */
    public ClassFile ( String rn){
        super();
        if ( null == rn)
            throw new IllegalArgumentException("Null resource-name argument to `ClassFile' constructor.");
        else {

            java.io.InputStream rin = null;

            try {
                Class cla = getClass();

                rin = cla.getResourceAsStream(rn);

                read(rin);
            }
            catch ( IllegalArgumentException ilarg){
                throw new IllegalArgumentException("Class file resource not found ("+rn+").");
            }
            catch ( NullPointerException npx){
                throw new IllegalArgumentException("Class file not found ("+rn+").");
            }
            catch ( java.io.IOException iox){
                iox.printStackTrace();
                throw new IllegalArgumentException("Class file format error in ("+rn+") is ("+iox.getMessage()+").");
            }
            finally {
                if ( null != rin) try{rin.close();}catch(java.io.IOException iox){}
            }
        }
    }



    /**
     * Class name
     */
    public String getName(){
    	return constant_pool[cpClass().arg1_idx].strValue.replace('/','.');
    }
    /**
     * Super class name
     */
    public String getNameSuper(){
    	return constant_pool[cpSuperClass().arg1_idx].strValue.replace('/','.');
    }
    protected CPInfo[] pool(){
        return constant_pool;
    }
    public String pool(int idx){
        return constant_pool[idx].toString(this.constant_pool).replace('/','.');
    }
    public String poolClass(int idx){
        CPInfo cpClass = constant_pool[idx];
        return cpClass.getArg1(this);
    }
    public String poolName(int idx){
        CPInfo cpClass = constant_pool[idx];
        return cpClass.getArg1(this);
    }
    public String poolType(int idx){
        CPInfo cpClass = constant_pool[idx];
        return cpClass.getArg2(this);
    }
    /**
     * Rename this class
     */
    public void renameClass ( String classname){
        constant_pool[cpClass().arg1_idx].strValue = classname.replace('.','/');
    }
    /**
     * Rename super class
     */
    public void renameSuper ( String classname){
        constant_pool[cpSuperClass().arg1_idx].strValue = classname.replace('.','/');
    }
    /**
     * Rename class as a subclass of the current class.
     */
    public void subClass ( String classname){

        CPInfo cla = cpClass(), supcla = cpSuperClass();

        constant_pool[supcla.arg1_idx].strValue = constant_pool[cla.arg1_idx].strValue;

        constant_pool[cla.arg1_idx].strValue = classname.replace('.','/');

    }

    // "rename" lookup consts
    private final static int REN_FROM_CN = 1;
    private final static int REN_FROM_CR = 2;
    private final static int REN_FROM_MN = 3;
    private final static Integer REN_FROM_CN_I = new Integer(REN_FROM_CN);
    private final static Integer REN_FROM_CR_I = new Integer(REN_FROM_CR);
    private final static Integer REN_FROM_MN_I = new Integer(REN_FROM_MN);



    /**
     * For two methods of identical type signatures ("from" and "to"),
     * redirect the invocation of "from" to the invocation of "to".
     * 
     * <pre>
     *  from = "alto.pi.pif.pica_.pica_fun"
     *  to   = "net.pico.brazil.picoFun"
     *
     *  For:
     *
     *    package alto.pi
     *    class pica_ { 
     *      public static Object pica_fun ( Object arg)
     *    }
     *
     *  And:
     *
     *    package net.pico
     *    class brazil { 
     *      public static Object picoFun ( Object arg)
     *    } 
     * </pre>
     * 
     * <p> When either method name parameter ("from" or "to") is not
     * qualified with a class name, <i>this</i> class name is used as a
     * default.  Valid unqualified method names include both
     * ".methodname" and "methodname".
     * 
     * @param from Fully- qualified class- method name
     * 
     * @param to Fully- qualified class- method name 
     * 
     * @exception java.util.NoSuchElementException For method reference "from" not found.  
     * 
     * @returns Number of principal constants modified.  */
    public int renameMethodRef ( String from, String to){

        CPInfo metrefs[] = cpMethodRefs();

        if ( null == metrefs)
            return 0;
        else {
            java.util.Hashtable lookup = new java.util.Hashtable();

            String from_cn, from_cnr, from_mn,
                to_cn, to_cnr, to_mn;

            int ix = from.lastIndexOf('.');

            if ( 0 > ix){
                from = getName()+"."+from;
                ix = from.lastIndexOf('.');
            }
            else if ( 0 == ix){
                from = getName()+from;
                ix = from.lastIndexOf('.');
            }

            from_cn = from.substring(0,ix);
            from_cnr = from_cn.replace('.','/');
            from_mn = from.substring(ix+1);

            lookup.put( from_cn, REN_FROM_CN_I);
            lookup.put( from_cnr, REN_FROM_CR_I);
            lookup.put( from_mn, REN_FROM_MN_I);


            ix = to.lastIndexOf('.');

            if ( 0 > ix){
                to = getName()+"."+to;
                ix = to.lastIndexOf('.');
            }
            else if ( 0 == ix){
                to = getName()+to;
                ix = to.lastIndexOf('.');
            }

            to_cn = to.substring(0,ix);
            to_cnr = to_cn.replace('.','/');
            to_mn = to.substring(ix+1);

            String mr_cn, mr_mn;

            CPInfo metref, mrcla, mrfld, pool[] = constant_pool;

            int len = metrefs.length, changes = 0;

            Integer lv;

            for ( short cc = 0; cc < len; cc++){

                metref = metrefs[cc];

                mrcla = pool[pool[metref.arg1_idx].arg1_idx];

                mr_cn = mrcla.strValue;

                if ( null != mr_cn ){

                    mrfld = pool[pool[metref.arg2_idx].arg1_idx];

                    mr_mn = mrfld.strValue;

                    if ( null != (lv = (Integer)lookup.get(mr_cn))){
		
                        switch(lv.intValue()){
		    
                        case REN_FROM_CR:

                            mrcla.strValue = to_cnr;

                            changes += 1;

                            break;

                        case REN_FROM_CN:

                            mrcla.strValue = to_cn;

                            changes += 1;

                            break;

                        default:
                            throw new IllegalStateException("BBBUGGG in `ClassFile.renameMethodRef' lookup types, found type ("+lv+").");
                        }
                    }

                    if ( null != (lv = (Integer)lookup.get(mr_mn))){
		
                        switch(lv.intValue()){
		    
                        case REN_FROM_MN:

                            mrfld.strValue = to_mn;
			
                            changes += 1;

                            break;

                        default:
                            throw new IllegalStateException("BBBUGGG in `ClassFile.renameMethodRef' lookup types, found type ("+lv+").");
                        }
                    }
                }
            }

            if ( 1 > changes)
                throw new java.util.NoSuchElementException("Method reference ("+from+") not found.");
            else
                return changes;
        }
    }


    /**
     * For two fields of identical types, redirect the reference of
     * "from" to the reference of "to".
     * 
     * <p> When either field name parameter ("from" or "to") is not
     * qualified with a class name, this class name is used as a
     * default.  Valid unqualified field names include both
     * ".fieldname" and "fieldname".
     * 
     * @param from Fully- qualified class- field name, eg,
     * <tt>"alto.pi.pif.ClassFile.constant_pool"</tt>.
     * 
     * @param to Fully- qualified class- field name 
     * 
     * @exception java.util.NoSuchElementException For field reference "from" not found.  
     * 
     * @returns Number of principal constants modified.  */
    public int renameFieldRef ( String from, String to){

        CPInfo fldrefs[] = cpFieldRefs();

        if ( null == fldrefs)
            return 0;
        else {
            java.util.Hashtable lookup = new java.util.Hashtable();

            String from_cn, from_cnr, from_fn,
                to_cn, to_cnr, to_fn;

            int ix = from.lastIndexOf('.');

            if ( 0 > ix){
                from = getName()+"."+from;
                ix = from.lastIndexOf('.');
            }
            else if ( 0 == ix){
                from = getName()+from;
                ix = from.lastIndexOf('.');
            }

            from_cn = from.substring(0,ix);
            from_cnr = from_cn.replace('.','/');
            from_fn = from.substring(ix+1);

            lookup.put( from_cn, REN_FROM_CN_I);
            lookup.put( from_cnr, REN_FROM_CR_I);
            lookup.put( from_fn, REN_FROM_MN_I);


            ix = to.lastIndexOf('.');

            if ( 0 > ix){
                to = getName()+"."+to;
                ix = to.lastIndexOf('.');
            }
            else if ( 0 == ix){
                to = getName()+to;
                ix = to.lastIndexOf('.');
            }

            to_cn = to.substring(0,ix);
            to_cnr = to_cn.replace('.','/');
            to_fn = to.substring(ix+1);

            String fr_cn, fr_fn;

            CPInfo fldref, frcla, frfld, pool[] = constant_pool;

            int len = fldrefs.length, changes = 0;

            Integer lv;

            for ( short cc = 0; cc < len; cc++){

                fldref = fldrefs[cc];

                frcla = pool[pool[fldref.arg1_idx].arg1_idx];

                frfld = pool[pool[fldref.arg2_idx].arg1_idx];

                fr_cn = frcla.strValue;

                fr_fn = frfld.strValue;

                if ( null != (lv = (Integer)lookup.get(fr_cn))){
		
                    switch(lv.intValue()){
		    
                    case REN_FROM_CR:

                        frcla.strValue = to_cnr;

                        changes += 1;

                        break;

                    case REN_FROM_CN:

                        frcla.strValue = to_cn;

                        changes += 1;

                        break;

                    default:
                        throw new IllegalStateException("BBBUGGG in `ClassFile.renameMethodRef' lookup types, found type ("+lv+").");
                    }
                }

                if ( null != (lv = (Integer)lookup.get(fr_fn))){
		
                    switch(lv.intValue()){
		    
                    case REN_FROM_MN:
			
                        frfld.strValue = to_fn;
			
                        changes += 1;

                        break;

                    default:
                        throw new IllegalStateException("BBBUGGG in `ClassFile.renameMethodRef' lookup types, found type ("+lv+").");
                    }
                }
            }

            if ( 1 > changes)
                throw new java.util.NoSuchElementException("Field reference ("+from+") not found.");
            else
                return changes;
        }
    }

    /**
     * Replace all strings matching "from" with "to".
     * 
     * @param from Existing string value 
     * 
     * @param to Replacement (new) string value
     * 
     * @returns Number of principal constants modified, zero for no
     * mods.
     */
    public int replaceString ( String from, String to){

        CPInfo cpi, pool[] = constant_pool;

        int len = pool.length, changes = 0;

        for ( short cc = 0; cc < len; cc++){

            cpi = pool[cc];

            if ( TYPE_STRING == cpi.type){

                cpi = pool[cpi.arg1_idx];

                if ( from.equals(cpi.strValue)){

                    cpi.strValue = to;

                    changes += 1;
                }
            }
        }

        return changes;
    }

    /**
     * @param mn Method name
     * 
     * @returns If the method is static 
     * 
     * @exception java.util.NoSuchElementException No method
     */
    public boolean isMethodStatic ( String mn){
        MethodInfo met = method(mn);

        if ( null == met)
            throw new java.util.NoSuchElementException("Method ("+mn+") not found.");

        else if ( ACC_STATIC == (met.access_flags & ACC_STATIC))
	    
            return true;
        else
            return false;
    }



    public FieldInfo[] cpFields(){
        return this.fields;
    }
    public MethodInfo[] cpMethods(){
        return this.methods;
    }

    /**
     * @returns This CLASS cpi
     */
    public CPInfo cpClass(){
        return constant_pool[class_this_idx];
    }

    /**
     * @returns Super CLASS cpi
     */
    public CPInfo cpSuperClass(){
        return constant_pool[class_super_idx];
    }

    /**
     * @returns METHODREF and IFMETREF cpi's
     */
    public CPInfo[] cpMethodRefs(){

        CPInfo cpi, pool[] = constant_pool, ret[] = null, copier[];

        int poolen = pool.length, rix = 0, tt;

        for ( short cc = 0; cc < poolen; cc++){

            cpi = pool[cc];

            tt = cpi.type;

            if ( TYPE_METHODREF == tt || TYPE_IFMETREF == tt){
                if ( null == ret)
                    ret = new CPInfo[1];
                else {
                    copier = new CPInfo[rix+1];
                    System.arraycopy(ret,0,copier,0,rix);
                    ret = copier;
                }
                ret[rix++] = cpi;
            }
        }
        return ret;
    }

    /**
     * @param mn Method name
     *
     * @returns First METHODREF or IFMETREF matching the argument method name.
     */
    public CPInfo cpMethodRef( String mn){

        CPInfo cp1, cp2, pool[] = constant_pool;

        int poolen = pool.length, tt;

        for ( short cc = 0; cc < poolen; cc++){

            cp1 = pool[cc];

            tt = cp1.type;

            if ( TYPE_METHODREF == tt || TYPE_IFMETREF == tt){

                cp2 = pool[pool[cp1.arg2_idx].arg1_idx]; // name

                if ( mn.equals(cp2.strValue))
                    return cp1;
            }
        }
        return null;
    }

    /**
     * @param mn Method name
     *
     * @returns First method matching the argument method name.
     */
    public MethodInfo method( String mn){

        if ( null == methods)
            return null;
        else {

            MethodInfo met, mets[] = methods;

            int len = mets.length;

            for ( short cc = 0; cc < len; cc++){

                met = mets[cc];

                if ( mn.equals( met.getName(constant_pool).strValue))
                    return met;
            }
        }
        return null;
    }

    /**
     * @returns FIELDREF cpi's
     */
    public CPInfo[] cpFieldRefs(){

        CPInfo cpi, pool[] = constant_pool, ret[] = null, copier[];

        int poolen = pool.length, rix = 0;

        for ( short cc = 0; cc < poolen; cc++){

            cpi = pool[cc];

            if ( TYPE_FIELDREF == cpi.type){
                if ( null == ret)
                    ret = new CPInfo[1];
                else {
                    copier = new CPInfo[rix+1];
                    System.arraycopy(ret,0,copier,0,rix);
                    ret = copier;
                }
                ret[rix++] = cpi;
            }
        }
        return ret;
    }

    /**
     * @param fn Field name
     *
     * @returns First FIELDREF matching the argument field name.
     */
    public CPInfo cpFieldRef( String fn){

        CPInfo cp1, cp2, pool[] = constant_pool;

        int poolen = pool.length;

        for ( short cc = 0; cc < poolen; cc++){

            cp1 = pool[cc];

            if ( TYPE_FIELDREF == cp1.type){

                cp2 = pool[pool[cp1.arg2_idx].arg1_idx]; // name

                if ( fn.equals(cp2.strValue))
                    return cp1;
            }
        }
        return null;
    }



    public String toString(){
        return toString(false);
    }
    public String toString( boolean nesting){

        StringBuilder strbuf = new StringBuilder();

        strbuf.append("\tCAFEBABE ");
        strbuf.append(Integer.toString(version_major));
        strbuf.append('.');
        strbuf.append(Integer.toString(version_minor));

        strbuf.append("\n\tClass      ");
        if (nesting)
            strbuf.append(constant_pool[class_this_idx].toString(constant_pool));
        else
            strbuf.append(constant_pool[class_this_idx].toString());

        strbuf.append("\n\tSuperclass ");
        if (nesting)
            strbuf.append(constant_pool[class_super_idx].toString(constant_pool));
        else
            strbuf.append(constant_pool[class_super_idx].toString());

        if ( null != interface_idxs){

            for ( int cc = 0; cc < interface_idxs.length; cc++){

                strbuf.append("\n\tInterface  ");
                if (nesting)
                    strbuf.append(constant_pool[interface_idxs[cc]].toString(constant_pool));
                else
                    strbuf.append(constant_pool[interface_idxs[cc]].toString());
            }
        }
        if ( null != fields){

            for ( int cc = 0; cc < fields.length; cc++){

                strbuf.append("\n\t");
                if (nesting)
                    strbuf.append(fields[cc].toString(constant_pool));
                else
                    strbuf.append(fields[cc].toString());
            }
        }
        if ( null != methods){

            for ( int cc = 0; cc < methods.length; cc++){

                strbuf.append("\n\t");
                if (nesting)
                    strbuf.append(methods[cc].toString(constant_pool));
                else
                    strbuf.append(methods[cc].toString());
            }
        }

        strbuf.append('\n');

        return strbuf.toString();
    }

    public short indexOf( CPInfo cpi) throws java.util.NoSuchElementException {

        CPInfo[] pool = constant_pool;

        int poolen = pool.length;

        for ( short cc = 0; cc < poolen; cc++){

            if (cpi == pool[cc])
                return cc;
        }
        throw new java.util.NoSuchElementException("CP ("+cpi+") not found.");
    }

    /**
     * Read a class.
     * @returns Success or failure
     * @exception Format or I/O error
     */
    public void read(java.io.InputStream in) throws java.io.IOException {
        try {
            _ok = false;

            java.io.DataInputStream din;

            if (in instanceof java.io.DataInputStream)
                din = (java.io.DataInputStream)in;
            else
                din = new java.io.DataInputStream(in);

            /*
             * Head
             */
            magic = din.readInt();

            if (magic != (int) 0xCAFEBABE) throw new java.io.IOException("Input not a valid java class file.");
	
            version_major = din.readShort();

            version_minor = din.readShort();

            int cc, count;

            /*
             * Read constant pool
             */
            count = din.readShort();

            CPInfo cpi;

            constant_pool = new CPInfo[count];

            constant_pool[0] = new CPInfo(); // natively indexed from `1'

            for ( cc = 1; cc < constant_pool.length; cc++){

                constant_pool[cc] = cpi = new CPInfo();

                cpi.read(din);

                if (cpi.type == TYPE_LONG || cpi.type == TYPE_DOUBLE) cc++; // 2 slots
            }

            /*
             * Flags
             */
            access_flags = din.readShort();

            /*
             * Identity
             */
            class_this_idx = din.readShort();

            class_super_idx = din.readShort();

            /*
             * Interfaces
             */
            count = din.readShort();

            if ( 0 < count){

                interface_idxs = new short[count];

                for ( cc = 0; cc < count; cc++){

                    interface_idxs[cc] = din.readShort();

                }
            }

            /*
             * Fields
             */
            count = din.readShort();

            if ( 0 < count){

                FieldInfo fi ;

                fields = new FieldInfo[count];

                for ( cc = 0; cc < count; cc++){

                    fields[cc] = fi = new FieldInfo();

                    fi.read(din);
                }
            }

            /*
             * Methods
             */
            count = din.readShort();

            if ( 0 < count){

                MethodInfo mi;

                methods = new MethodInfo[count];

                for ( cc = 0; cc < count; cc++){

                    methods[cc] = mi = new MethodInfo();

                    mi.read(din);
                }
            }

            /*
             * Attributes
             */
            count = din.readShort();

            if ( 0 < count){

                AttrInfo at;

                attributes = new AttrInfo[count];

                for ( cc = 0; cc < count; cc++){

                    at = new AttrInfo();

                    attributes[cc] = at;

                    at.read(din);
                }
            }

            _ok = true;
        }
        finally {
            in.close();
        }
    }

    public void write(java.io.OutputStream out)	throws java.io.IOException {

        if (!_ok)
            throw new java.io.IOException("Can't write class file, bad format.");
        else {
            int cc, count;

            java.io.DataOutputStream dout;

            if ( out instanceof java.io.DataOutputStream)
                dout = (java.io.DataOutputStream)out;
            else
                dout = new java.io.DataOutputStream(out);

            /*
             * Head
             */
            dout.writeInt(magic);
            dout.writeShort(version_major);
            dout.writeShort(version_minor);

            /*
             * Constant pool
             */
            count = constant_pool.length;

            dout.writeShort(count);

            if ( 0 < count){
                CPInfo cpi;

                for ( cc = 1; cc < count; cc++){

                    cpi = constant_pool[cc];

                    cpi.write(dout);
                }
            }

            /*
             * Flags
             */
            dout.writeShort(access_flags);

            /*
             * Identity
             */
            dout.writeShort( class_this_idx);

            dout.writeShort( class_super_idx);

            /*
             * Interfaces
             */
            count = (null == interface_idxs)?(0):(interface_idxs.length);

            dout.writeShort(count);

            if ( 0 < count){

                for ( cc = 0; cc < count; cc++){

                    dout.writeShort( interface_idxs[cc]);
                }
            }

            /*
             * Fields
             */
            count = (null == fields)?(0):(fields.length);

            dout.writeShort(count);

            if ( 0 < count){

                for ( cc = 0; cc < count; cc++)
                    fields[cc].write(dout);
            }

            /*
             * Methods
             */
            count = (null == methods)?(0):(methods.length);

            dout.writeShort(count);
	    
            if ( 0 < count){
                for ( cc = 0; cc < count; cc++)
                    methods[cc].write(dout);
            }

            /*
             * Attributes
             */
            count = (null == attributes)?(0):(attributes.length);

            dout.writeShort(count);
	    
            if ( 0 < count){

                for ( cc = 0; cc < count; cc++)
                    attributes[cc].write(dout);
            }
        }
    }


    /**
     * @param cla Print ClassFile
     *
     * @param out Destination
     */
    public final static void PrintCP ( ClassFile cla, java.io.PrintStream out){
        PrintCP(cla,out,false);
    }
    /**
     * @param cla Print ClassFile
     *
     * @param out Destination
     * 
     * @param nesting When true, print tree nodes in- line.
     */
    public final static void PrintCP ( ClassFile cla, java.io.PrintStream out, boolean nesting){

        out.println(cla.toString(nesting));

        CPInfo cpi, cp[] = cla.constant_pool;

        int count = cp.length;

        for ( int cc = 1; cc < count; cc++){
            cpi = cp[cc];

            out.write('\t');

            out.print(Integer.toString(cc));

            out.write(':');

            out.write('\t');

            if (nesting)
                out.println(cpi.toString(cp));
            else
                out.println(cpi.toString());
        }
    }

    private final static void usage ( java.io.PrintStream out){
        out.println();
        out.println(" Usage");
        out.println();
        out.println("\tClassFile -c classname [-i]");
        out.println("\tClassFile -f filename.class [-i]");
        out.println("\tClassFile [ -c ... | -f ... ] -r from to ");
        out.println();
        out.println(" Description");
        out.println();
        out.println("\tTest `ClassFile'.  The first two forms print the");
        out.println("\tconstant pool table for visualizing what's ");
        out.println("\tgoing on there.");
        out.println();
        out.println("\tThe third form will rename a method and class");
        out.println("\treferences as `renameMethodRef', then save a");
        out.println("\tclass file in this directory or with `file'.");
        out.println();
        out.println("\tThe `-i' option turns- on inline tree nodes ");
        out.println("\tfor printing (no `-r').");
        out.println();
    }

    /**
     * Command line test- tool.
     * 
     * <pre>
     *  Usage
     * 
     *         ClassFile -c classname [-i]
     *         ClassFile -f filename.class [-i]
     *         ClassFile [ -c ... | -f ... ] -r from to 
     * 
     *  Description
     * 
     *         Test `ClassFile'.  The first two forms print the
     *         constant pool table for visualizing what's 
     *         going on there.
     * 
     *         The third form will rename a method and class
     *         references as `renameMethodRef', then save a
     *         class file in this directory or with `file'.
     * 
     *         The `-i' option turns- on inline tree nodes 
     *         for printing (no `-r').
     * 
     * </pre>
     */
    public final static void main ( String[] argv){
        try {
            boolean print = false, inline = false;

            java.io.File file = null;
            String rsrc = null;

            String from = null, to = null;

            { 
                String arg;
                int alen = argv.length;

                for ( int argc = 0; argc < alen; argc++){

                    arg = argv[argc];

                    switch(arg.charAt(0)){

                    case '-':
                    case '/':
                        switch(arg.charAt(1)){

                        case 'h':
                        case 'H':
                        case '?':
                            throw new IllegalArgumentException();

                        case 'i':

                            if (print)
                                inline = true;
                            else
                                throw new IllegalArgumentException("Option `-i' must follow a print option, one of either `-f' or `-c'.");

                            break;

                        case 'f':
                            argc += 1;

                            if ( argc < alen){

                                arg = argv[argc];

                                file = new java.io.File(arg);

                                print = true;
			    
                                break;
                            }
                            else 
                                throw new IllegalArgumentException("Option `-f' requires filename argument.");
                        case 'c':
                            argc += 1;

                            if ( argc < alen){

                                arg = argv[argc];

                                rsrc = arg;

                                print = true;
			    
                                break;
                            }
                            else 
                                throw new IllegalArgumentException("Option `-p' requires classname argument.");

                        case 'r':

                            print = false;

                            argc += 1;

                            if ( argc < alen){

                                from = argv[argc];

                                argc += 1;

                                if ( argc < alen){

                                    to = argv[argc];

                                    break;
                                }
                                else 
                                    throw new IllegalArgumentException("Option `-r' requires 'to' argument.");
                            }
                            else 
                                throw new IllegalArgumentException("Option `-r' requires 'from' argument.");


                        default:
                            throw new IllegalArgumentException("Unrecognized option `"+arg+"'.");
                        }
                        break;
                    default:
                        throw new IllegalArgumentException("Unrecognized option `"+arg+"'.");
                    }
                }
            }

            long start; 

            ClassFile clr = null;
	    
            if ( null != file){

                java.io.FileInputStream fin = new java.io.FileInputStream(file);

                start = System.currentTimeMillis();

                clr = new ClassFile(fin);

                System.err.println("Pica setup "+(System.currentTimeMillis()-start)+" ms -- using `new ClassFile(InputStream)'.");

                fin.close();
            }
            else if ( null != rsrc){

                start = System.currentTimeMillis();

                clr = new ClassFile(rsrc);

                System.err.println("Pica setup "+(System.currentTimeMillis()-start)+" ms -- using `new ClassFile(String)'.");
            }
            else
                throw new IllegalArgumentException();

            if ( print)

                PrintCP( clr, System.out, inline);
	    
            else if ( null != from && null != to){

                String cn = to.substring(0,to.lastIndexOf('.'));

                cn = cn.substring(cn.lastIndexOf('.')+1);

                cn = "pica/t_"+cn;

                if ( null != file)
                    file = new java.io.File( file.getParent()+"/"+ cn+".class");
                else
                    file = new java.io.File( cn.replace('/','.')+".class");

                java.io.FileOutputStream fout = new java.io.FileOutputStream (file);

                start = System.currentTimeMillis();

                clr.renameMethodRef( from, to);

                clr.subClass( cn);

                System.err.println("Pica rename & subclass "+(System.currentTimeMillis()-start)+" ms.");

                start = System.currentTimeMillis();

                clr.write(fout);

                System.err.println("Pica write "+(System.currentTimeMillis()-start)+" ms.");

                System.out.println("Wrote "+file.getAbsolutePath());

                fout.close();
            }
            else
                throw new IllegalArgumentException();

            System.exit(0);
        }
        catch ( IllegalArgumentException ilarg){

            String msg = ilarg.getMessage();

            if ( null == msg)

                usage(System.err);
            else 
                System.err.println(msg);

            System.exit(1);
        }
        catch ( Exception exc){

            exc.printStackTrace();

            System.exit(1);
        }
    }
}
